/*	http://code.google.com/p/wamon/
 *	Windows Activity Monitor - Monitors user's activity on the computer, 
 *	tracks active windows and processes, allows table and chart visualization.
 *	Copyright (C) 2009  Archae s.r.o. (http://archaedev.com/)
 *
 *	This program is free software: you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation, either version 3 of the License, or
 *	(at your option) any later version.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Web;

/**
 * Usage:
 * HttpServer http = new HttpServer();
 * http.Start(12345);
 * http.AddHandler("home", new HttpRequestHandlerDelegate(OnHome));
 * ...
 * http.Stop();
 * 
 * private int OnHome(HttpReqResp reqResp)
 * {
 *     reqResp.WriteLine("<html><body>" + reqResp["hello"] + "</body></html>");
 *     return 200;
 * }
 */
namespace Archae.HttpInterface
{
	public delegate int HttpRequestHandlerDelegate(HttpReqResp reqResp);
	public class HttpServer
	{
		TcpListener listener = null;
		bool exitThread;
		Dictionary<string, HttpRequestHandlerDelegate> handlers = new Dictionary<string, HttpRequestHandlerDelegate>();
		Dictionary<int, string> statusCodes = new Dictionary<int, string>();
		int clientCount = 0;
		public HttpServer()
		{
			statusCodes[200] = "OK";
			statusCodes[301] = "Moved Permanently";
			statusCodes[400] = "Bad Request";
			statusCodes[403] = "Forbidden";
			statusCodes[404] = "Not Found";
		}
		public bool Start(int port, bool loopback)
		{
			clientCount = 0;
			if (listener != null)
				Stop();
			try
			{
				if (loopback)
					listener = new TcpListener(IPAddress.Loopback, port);
				else
					listener = new TcpListener(IPAddress.Any, port);
				listener.Start();
			}
			catch (Exception) { return false; }
			exitThread = false;
			new Thread(new ThreadStart(ListenerThread)).Start();
			return true;
		}
		public void Stop()
		{
			exitThread = true;
			listener.Stop();
			while (listener != null)
				Thread.Sleep(100);
		}
		public void AddHandler(string token, HttpRequestHandlerDelegate handlerDelegate)
		{
			handlers[token] = handlerDelegate;
		}
		private void ListenerThread()
		{
			while (!exitThread)
			{
				Thread threadListener = new Thread(new ParameterizedThreadStart(HandlerThread));
				threadListener.SetApartmentState(ApartmentState.STA);
				try { threadListener.Start(listener.AcceptTcpClient()); }
				catch (Exception) { }
			}
			listener = null;
		}
		private void HandlerThread(object obj)
		{
			if (clientCount > 9)
				return;
			clientCount++;
			TcpClient client = (TcpClient)obj;
			Stream stream = client.GetStream();
			try
			{
				StreamReader reader = new StreamReader(stream);
				String line = reader.ReadLine();
				line = line.Split(' ')[1].Substring(1);
				String token = (line.IndexOf('?') == -1 ? line : line.Substring(0, line.IndexOf('?')));
				String query = (line.IndexOf('?') == -1 ? string.Empty : line.Substring(line.IndexOf('?') + 1));
				string host = "";
				int contentLength = 0;
				while ((line = reader.ReadLine()) != null)
				{
					if (line.StartsWith("Host:"))
						host = line.Substring(5).Trim().Split(':')[0];
					if (line.StartsWith("Content-Length:"))
						contentLength = Int32.Parse(line.Substring(15).Trim());
					else if (line == "")
					{
						if (contentLength != 0)
						{
							char[] buffer = new char[contentLength];
							reader.Read(buffer, 0, contentLength);
							query += (query == string.Empty ? "" : "&") + new string(buffer);
						}
						break;
					}
				}

				HttpReqResp reqResp = new HttpReqResp(host, token, query);
				reqResp.SetHeader("Cache-Control", "no-cache");
				int statusCode = (handlers.ContainsKey(token) ? handlers[token](reqResp) : 404);
				WriteString("HTTP/1.1 " + statusCode + " " + statusCodes[statusCode] + "\r\n", stream);
				WriteString("Server: aHTTP 1.1\r\n", stream);
				reqResp.SetHeader("Content-Length", reqResp.Response.Length.ToString());
				for (int i = 0; i < reqResp.Headers.Count; i++)
					WriteString(reqResp.Headers.GetKey(i) + ": " + reqResp.Headers.Get(i) + "\r\n", stream);
				WriteString("\r\n", stream);
				try { stream.Write(reqResp.Response.GetBuffer(), 0, (int)reqResp.Response.Length); }
				catch { }
			}
			catch { }
			stream.Flush();
			stream.Close();
			client.Close();
			clientCount--;
		}
		private void WriteString(string str, Stream stream)
		{
			byte[] bytes = Encoding.UTF8.GetBytes(str);
			try { stream.Write(bytes, 0, bytes.Length); }
			catch (Exception) { }
		}
	}
	public class HttpReqResp
	{
		string host;
		string script;
		NameValueCollection parameters;
		NameValueCollection headers;
		MemoryStream response;
		internal HttpReqResp(string host, string script, string query)
		{
			this.host = host;
			this.script = script;
			parameters = HttpUtility.ParseQueryString(query);
			headers = new NameValueCollection();
			response = new MemoryStream();
		}
		public string Host
		{
			get { return host; }
		}
		public string Script
		{
			get { return script; }
		}
		public string this[string name]
		{
			get { return parameters[name]; }
		}
		internal NameValueCollection Headers
		{
			get { return headers; }
		}
		internal MemoryStream Response
		{
			get { return response; }
		}
		public void SetHeader(string name, string value)
		{
			headers.Add(name, value);
		}
		public void Write(byte[] bytes)
		{
			response.Write(bytes, 0, bytes.Length);
		}
		public void Write(string str)
		{
			Write(Encoding.UTF8.GetBytes(str));
		}
		public void WriteLine(string str)
		{
			Write(str + "\r\n");
		}
	}
}